#ifndef _QUEUE_H_
#define _QUEUE_H_

#include <iostream>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <cassert>
#include <string>
#include <map>
#include <vector>
#include <queue>

/**
    基于命名管道的通信队列实现
*/

namespace sp{

// 结合共享内存用于进程间通信，也可用于进程内部通信。
template<class T>
class BlockingQueue{
private:
    uint8_t type;           // 类型，进程内通信，或者进程间通信
    std::string fifo_name;  // 进程间通信，命名管道名称
    int pipe_fd;            // 进程间通信，命名管道描述符
    std::queue<T> Q;        // 进程内通信，队列
    int task_id;            // 当前function id
    int next_task_id;       // 下一个function id
    // 禁用拷贝
    BlockingQueue(const BlockingQueue&) = delete;
    BlockingQueue& operator= (const BlockingQueue&) = delete;
public:
    BlockingQueue(int task_id, int next_task_id);            // 新建一个Queue，不确定是什么类型，需要搭配setType一起使用
    BlockingQueue(std::string fifo_name);                    // 创建命名管道，进程间通信
    BlockingQueue(int task_id, int next_task_id, std::map<int, std::vector<int>>& D_table); // 根据D_table决定是否创建命名管道，进程内或进程间通信。
    ~BlockingQueue();
    void setType(std::map<int, std::vector<int>>& D_table);  // 根据D_table设置类型
    std::string getName();                 // 得到命名管道名称
    void open_named_pipe(char attr);       // 打开命名管道
    bool dequeue(T& data);                 // 读取数据，非阻塞     
    void wait_dequeue(T& data);            // 读取数据，阻塞
    bool dequeue(T data, int len);         // 读取字符串，非阻塞
    void wait_dequeue(T data, int len);    // 读取字符串，阻塞
    bool enqueue(T data);                  // 发送数据，非阻塞
    void wait_enqueue(T data);             // 发送数据，阻塞
    bool enqueue(T data, int len);         // 发送字符串，非阻塞
    void wait_enqueue(T data, int len);    // 发送字符串，阻塞
};

template<class T>
BlockingQueue<T>::BlockingQueue(int task_id, int next_task_id){
    type = INTRA_PROCESS;
    pipe_fd = -1;
    this->task_id = task_id;
    this->next_task_id = next_task_id;
}

template<class T>
BlockingQueue<T>::BlockingQueue(std::string fifo_name){
    type = INTER_PROCESS;
    this->fifo_name = fifo_name;
    // 创建命名管道
    int ret = mkfifo(fifo_name.c_str(), 0666);
    if(ret < 0){
        if(errno != EEXIST){
            std::cout << fifo_name << " named fifo created failed." << std::endl;
            std::cout << strerror(errno) << std::endl;
            assert(false);
        }
    }
    // 初始化文件描述符
    pipe_fd = -1;
}

template<class T>
BlockingQueue<T>::BlockingQueue(int task_id, int next_task_id, std::map<int, std::vector<int>>& D_table){
    // 赋值
    this->task_id = task_id;
    this->next_task_id = next_task_id;
    // 判断两个Task是不是在同一进程内
    this->type = INTER_PROCESS;
    for(auto i : D_table){
        if(std::find(i.second.begin(), i.second.end(), task_id) != i.second.end() && std::find(i.second.begin(), i.second.end(), next_task_id) != i.second.end()){
            this->type = INTRA_PROCESS;
            break;
        }
    }
    if(type == INTER_PROCESS){
        this->fifo_name = "queues/q_" + std::to_string(task_id) + "_" + std::to_string(next_task_id);
        // 创建命名管道
        int ret = mkfifo(fifo_name.c_str(), 0666);
        if(ret < 0){
            if(errno != EEXIST){
                std::cout << fifo_name << " named fifo created failed." << std::endl;
                std::cout << strerror(errno) << std::endl;
                assert(false);
            }
        }
        // 初始化文件描述符
        pipe_fd = -1;
    }else if(type == INTRA_PROCESS){
        // 进程内部通信
        // 默认初始化queue，不需要额外操作
        this->fifo_name = "no_defined";
        this->pipe_fd = -1;
    }else{
        assert(false);
    }
}

template<class T>
BlockingQueue<T>::~BlockingQueue(){
    if(type == INTER_PROCESS){
        if(pipe_fd != -1){
            close(pipe_fd);
        }
    }
}

template<class T>
void BlockingQueue<T>::setType(std::map<int, std::vector<int>>& D_table){
    this->type = INTER_PROCESS;
    for(auto i : D_table){
        if(std::find(i.second.begin(), i.second.end(), task_id) != i.second.end() && std::find(i.second.begin(), i.second.end(), next_task_id) != i.second.end()){
            this->type = INTRA_PROCESS;
            break;
        }
    }
    if(type == INTER_PROCESS){
        this->fifo_name = "queues/q_" + std::to_string(task_id) + "_" + std::to_string(next_task_id);
        // 创建命名管道
        int ret = mkfifo(fifo_name.c_str(), 0666);
        if(ret < 0){
            if(errno != EEXIST){
                std::cout << fifo_name << " named fifo created failed." << std::endl;
                std::cout << strerror(errno) << std::endl;
                assert(false);
            }
        }
        // 初始化文件描述符
        pipe_fd = -1;
    }else if(type == INTRA_PROCESS){
        // 进程内部通信
        // 默认初始化queue，不需要额外操作
        this->fifo_name = "no_defined";
        this->pipe_fd = -1;
    }else{
        assert(false);
    }
}

template<class T>
std::string BlockingQueue<T>::getName(){
    assert(type == INTER_PROCESS);
    return fifo_name;
}

template<class T>
void BlockingQueue<T>::open_named_pipe(char attr){
    if(type == INTER_PROCESS){
        if(attr == 'w'){
            pipe_fd = open(fifo_name.c_str(), O_WRONLY | O_NONBLOCK); // 读取时非阻塞
        }else if(attr == 'r'){
            pipe_fd = open(fifo_name.c_str(), O_RDONLY | O_NONBLOCK); // 写入时非阻塞
        }else{
            assert(false);
        }
        if(pipe_fd < 0){
            std::cout << fifo_name << " " << attr << " pipe open failed." << std::endl;
            std::cout << strerror(errno) << std::endl;
            assert(false);
        }
    }else if(type == INTRA_PROCESS){
        // 默认支持读写
    }else{
        std::cout << type << std::endl;
        assert(false);
    }
}

// 非阻塞读取数据
template<class T>
bool BlockingQueue<T>::dequeue(T& data){  // 返回值为true代表读取成功，false代表读取失败。
    if(type == INTER_PROCESS){
        assert(pipe_fd != -1);
        int nread = 0;
        if((nread = read(pipe_fd, &data, sizeof(T))) < 0){
            // std::cout << fifo_name << " read failed." << std::endl;
            // std::cout << strerror(errno) << std::endl;
            // assert(false);
            return false;
        }else{
            if(nread > 0)
                return true;
            else
                return false;
        }
    }else if(type == INTRA_PROCESS){
        if(Q.empty())
            return false;
        data = Q.front();
        Q.pop();
        return true;
    }else{
        assert(false);
    }
}

// 阻塞读取数据
template<class T>
void BlockingQueue<T>::wait_dequeue(T& data){
    if(type == INTER_PROCESS){
        assert(pipe_fd != -1);
        int nread = 0;
        while(true){
            if((nread = read(pipe_fd, &data, sizeof(T))) < 0){
                std::cout << fifo_name << " read failed." << std::endl;
                std::cout << strerror(errno) << std::endl;
                assert(false);
            }else{
                if(nread > 0) // 读取成功
                    break;
            }
        }
    }else if(type == INTRA_PROCESS){
        while(Q.empty()); // 等到Q中有数据
        data = Q.front();
        Q.pop();
    }else{
        assert(false);
    }
}

// 非阻塞读取字符串
template<class T>
bool BlockingQueue<T>::dequeue(T data, int len){
    assert(type == INTER_PROCESS);
    assert(pipe_fd != -1);
    int nread = 0;
    if((nread = read(pipe_fd, data, len)) < 0){
        std::cout << fifo_name << " read failed." << std::endl;
        std::cout << strerror(errno) << std::endl;
        assert(false);
    }else{
        if(nread > 0)
            return true;
        else
            return false;
    }
}

// 阻塞读取字符串
template<class T>
void BlockingQueue<T>::wait_dequeue(T data, int len){
    assert(type == INTER_PROCESS);
    assert(pipe_fd != -1);
    int nread = 0;
    while(true){
        if((nread = read(pipe_fd, data, len)) < 0){
            // 
            // std::cout << fifo_name << " read failed." << std::endl;
            // std::cout << strerror(errno) << std::endl;
            // assert(false);
        }else{
            if(nread > 0)
                break;
            // nread > 0 说明读到数据
            // nread = 0 说明写端描述符未打开
            // nread < 0 说明写端描述符已打开，但未写入数据
        }
    }
}

// 非阻塞写入数据
template<class T>
bool BlockingQueue<T>::enqueue(T data){
    if(type == INTER_PROCESS){
        assert(pipe_fd != -1);
        int nwrite = 0;
        if((nwrite = write(pipe_fd, &data, sizeof(T))) < 0){
            std::cout << fifo_name << " write failed." << std::endl;
            std::cout << strerror(errno) << std::endl;
            assert(false);
        }else{
            if(nwrite > 0)
                return true;
            else
                return false;
        }
    }else if(type == INTRA_PROCESS){
        Q.push(data);
    }else{
        assert(false);
    }
}

// 阻塞写入数据
template<class T>
void BlockingQueue<T>::wait_enqueue(T data){
    if(type == INTER_PROCESS){
        assert(pipe_fd != -1);
        int nwrite = 0;
        while(true){
            if((nwrite = write(pipe_fd, &data, sizeof(T))) < 0){
                std::cout << fifo_name << " write failed." << std::endl;
                std::cout << strerror(errno) << std::endl;
                assert(false);
            }else{
                if(nwrite > 0)
                    break;
            }
        }
    }else if(type == INTRA_PROCESS){
        Q.push(data);
    }else{
        assert(false);
    }
}

// 非阻塞写入字符串
template<class T>
bool BlockingQueue<T>::enqueue(T data, int len){
    assert(type == INTER_PROCESS);
    assert(pipe_fd != -1);
    int nwrite = 0;
    if((nwrite = write(pipe_fd, data, len)) < 0){
        std::cout << fifo_name << " write failed." << std::endl;
        std::cout << strerror(errno) << std::endl;
        assert(false);
    }else{
        if(nwrite > 0)
            return true;
        else
            return false;
    }
}

// 阻塞写入字符串
template<class T>
void BlockingQueue<T>::wait_enqueue(T data, int len){
    assert(type == INTER_PROCESS);
    assert(pipe_fd != -1);
    int nwrite = 0;
    while(true){
        if((nwrite = write(pipe_fd, data, len)) < 0){
            std::cout << fifo_name << " write failed." << std::endl;
            std::cout << strerror(errno) << std::endl;
            assert(false);
        }else{
            if(nwrite > 0)
                break;
        }
    }
}

};

#endif